﻿namespace TNet
{
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.CompilerServices;
    using System.Text;
    using System.Threading;

    public class UPnP
    {
        private string mControlURL;
        private Thread mDiscover;
        private IPAddress mGatewayAddress = IPAddress.None;
        private string mGatewayURL;
        private List<int> mPorts = new List<int>();
        private string mServiceType;
        private Status mStatus;
        private List<Thread> mThreads = new List<Thread>();
        public string name = "TNetServer";

        public UPnP()
        {
            Thread item = new Thread(new ParameterizedThreadStart(this.ThreadDiscover));
            this.mDiscover = item;
            this.mThreads.Add(item);
            item.Start(item);
        }

        public void Close()
        {
            List<Thread> mThreads = this.mThreads;
            lock (mThreads)
            {
                int index = this.mThreads.size;
                while (index > 0)
                {
                    Thread thread = this.mThreads[--index];
                    if (thread != this.mDiscover)
                    {
                        thread.Abort();
                        this.mThreads.RemoveAt(index);
                    }
                }
            }
            int size = this.mPorts.size;
            while (size > 0)
            {
                int num3 = this.mPorts[--size];
                int port = num3 >> 8;
                bool tcp = (num3 & 1) == 1;
                this.Close(port, tcp, null);
            }
        }

        private void Close(int port, bool tcp, OnPortRequest callback)
        {
            int item = (port << 8) | (!tcp ? 0 : 1);
            if (((port > 0) && this.mPorts.Remove(item)) && (this.mStatus == Status.Success))
            {
                ExtraParams parameter = new ExtraParams {
                    callback = callback,
                    port = port,
                    protocol = !tcp ? ProtocolType.Udp : ProtocolType.Tcp,
                    action = "DeletePortMapping"
                };
                object[] objArray1 = new object[] { "<NewRemoteHost></NewRemoteHost>\n<NewExternalPort>", port, "</NewExternalPort>\n<NewProtocol>", !tcp ? "UDP" : "TCP", "</NewProtocol>\n" };
                parameter.request = string.Concat(objArray1);
                if (callback != null)
                {
                    parameter.th = new Thread(new ParameterizedThreadStart(this.CloseRequest));
                    List<Thread> mThreads = this.mThreads;
                    lock (mThreads)
                    {
                        this.mThreads.Add(parameter.th);
                        parameter.th.Start(parameter);
                    }
                }
                else
                {
                    this.CloseRequest(parameter);
                }
            }
            else if (callback != null)
            {
                callback(this, port, !tcp ? ProtocolType.Udp : ProtocolType.Tcp, false);
            }
        }

        private void CloseRequest(object obj)
        {
            this.SendRequest((ExtraParams) obj);
        }

        public void CloseTCP(int port)
        {
            this.Close(port, true, null);
        }

        public void CloseTCP(int port, OnPortRequest callback)
        {
            this.Close(port, true, callback);
        }

        public void CloseUDP(int port)
        {
            this.Close(port, false, null);
        }

        public void CloseUDP(int port, OnPortRequest callback)
        {
            this.Close(port, false, callback);
        }

        ~UPnP()
        {
            this.mDiscover = null;
            this.Close();
            this.WaitForThreads();
        }

        private bool GetControlURL(string url)
        {
            string response = Tools.GetResponse(WebRequest.Create(url));
            if (string.IsNullOrEmpty(response))
            {
                return false;
            }
            this.mServiceType = "WANIPConnection";
            int index = response.IndexOf(this.mServiceType);
            if (index == -1)
            {
                this.mServiceType = "WANPPPConnection";
                index = response.IndexOf(this.mServiceType);
                if (index == -1)
                {
                    return false;
                }
            }
            int num2 = response.IndexOf("</service>", index);
            if (num2 == -1)
            {
                return false;
            }
            int startIndex = response.IndexOf("<controlURL>", index, (int) (num2 - index));
            if (startIndex == -1)
            {
                return false;
            }
            startIndex += 12;
            num2 = response.IndexOf("</controlURL>", startIndex, (int) (num2 - startIndex));
            if (num2 == -1)
            {
                return false;
            }
            this.mControlURL = this.mGatewayURL + response.Substring(startIndex, num2 - startIndex);
            return true;
        }

        private void Open(int port, bool tcp, OnPortRequest callback)
        {
            int item = (port << 8) | (!tcp ? 0 : 1);
            if (((port > 0) && !this.mPorts.Contains(item)) && (this.mStatus != Status.Failure))
            {
                string str = Tools.localAddress.ToString();
                if (str != "127.0.0.1")
                {
                    this.mPorts.Add(item);
                    ExtraParams parameter = new ExtraParams {
                        callback = callback,
                        port = port,
                        protocol = !tcp ? ProtocolType.Udp : ProtocolType.Tcp,
                        action = "AddPortMapping"
                    };
                    object[] objArray1 = new object[] { "<NewRemoteHost></NewRemoteHost>\n<NewExternalPort>", port, "</NewExternalPort>\n<NewProtocol>", !tcp ? "UDP" : "TCP", "</NewProtocol>\n<NewInternalPort>", port, "</NewInternalPort>\n<NewInternalClient>", str, "</NewInternalClient>\n<NewEnabled>1</NewEnabled>\n<NewPortMappingDescription>", this.name, "</NewPortMappingDescription>\n<NewLeaseDuration>0</NewLeaseDuration>\n" };
                    parameter.request = string.Concat(objArray1);
                    parameter.th = new Thread(new ParameterizedThreadStart(this.OpenRequest));
                    List<Thread> mThreads = this.mThreads;
                    lock (mThreads)
                    {
                        this.mThreads.Add(parameter.th);
                    }
                    parameter.th.Start(parameter);
                }
            }
            else if (callback != null)
            {
                callback(this, port, !tcp ? ProtocolType.Udp : ProtocolType.Tcp, false);
            }
        }

        private void OpenRequest(object obj)
        {
            while (this.mStatus == Status.Searching)
            {
                Thread.Sleep(1);
            }
            this.SendRequest((ExtraParams) obj);
        }

        public void OpenTCP(int port)
        {
            this.Open(port, true, null);
        }

        public void OpenTCP(int port, OnPortRequest callback)
        {
            this.Open(port, true, callback);
        }

        public void OpenUDP(int port)
        {
            this.Open(port, false, null);
        }

        public void OpenUDP(int port, OnPortRequest callback)
        {
            this.Open(port, false, callback);
        }

        private bool ParseResponse(string response)
        {
            int index = response.IndexOf("LOCATION:", StringComparison.OrdinalIgnoreCase);
            if (index == -1)
            {
                return false;
            }
            index += 9;
            int num2 = response.IndexOf('\r', index);
            if (num2 == -1)
            {
                return false;
            }
            string url = response.Substring(index, num2 - index).Trim();
            int length = url.IndexOf("://");
            length = url.IndexOf('/', length + 3);
            this.mGatewayURL = url.Substring(0, length);
            return this.GetControlURL(url);
        }

        private void SendRequest(ExtraParams xp)
        {
            string str = (this.mStatus != Status.Success) ? null : this.SendRequest(xp.action, xp.request, 0x2710, 3);
            if (xp.callback != null)
            {
                xp.callback(this, xp.port, xp.protocol, !string.IsNullOrEmpty(str));
            }
            if (xp.th != null)
            {
                List<Thread> mThreads = this.mThreads;
                lock (mThreads)
                {
                    this.mThreads.Remove(xp.th);
                }
            }
        }

        private string SendRequest(string action, string content, int timeout, int repeat)
        {
            string[] textArray1 = new string[] { "<?xml version=\"1.0\"?>\n<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n<s:Body>\n<m:", action, " xmlns:m=\"urn:schemas-upnp-org:service:", this.mServiceType, ":1\">\n" };
            string s = string.Concat(textArray1);
            if (!string.IsNullOrEmpty(content))
            {
                s = s + content;
            }
            s = s + "</m:" + action + ">\n</s:Body>\n</s:Envelope>\n";
            byte[] bytes = Encoding.UTF8.GetBytes(s);
            string response = null;
            try
            {
                for (int i = 0; i < repeat; i++)
                {
                    WebRequest request = WebRequest.Create(this.mControlURL);
                    request.Timeout = timeout;
                    request.Method = "POST";
                    string[] textArray2 = new string[] { "\"urn:schemas-upnp-org:service:", this.mServiceType, ":1#", action, "\"" };
                    request.Headers.Add("SOAPACTION", string.Concat(textArray2));
                    request.ContentType = "text/xml; charset=\"utf-8\"";
                    request.ContentLength = bytes.Length;
                    request.GetRequestStream().Write(bytes, 0, bytes.Length);
                    response = Tools.GetResponse(request);
                    if (!string.IsNullOrEmpty(response))
                    {
                        return response;
                    }
                }
            }
            catch (Exception)
            {
            }
            return null;
        }

        private void ThreadDiscover(object obj)
        {
            Thread item = (Thread) obj;
            string s = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nST:upnp:rootdevice\r\nMAN:\"ssdp:discover\"\r\nMX:3\r\n\r\n";
            byte[] bytes = Encoding.ASCII.GetBytes(s);
            int port = 0x2710 + ((int) (DateTime.UtcNow.Ticks % 0xafc8L));
            List<IPAddress> localAddresses = Tools.localAddresses;
            for (int i = 0; i < localAddresses.size; i++)
            {
                IPAddress address = localAddresses[i];
                this.mStatus = Status.Searching;
                UdpClient client = null;
                try
                {
                    byte[] buffer2;
                    UdpClient client2 = new UdpClient(new IPEndPoint(address, port));
                    client2.Connect(IPAddress.Broadcast, 0x76c);
                    client2.Send(bytes, bytes.Length);
                    client2.Close();
                    client = new UdpClient(new IPEndPoint(address, port)) {
                        Client = { ReceiveTimeout = 0xbb8 }
                    };
                    IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
                    do
                    {
                        buffer2 = client.Receive(ref remoteEP);
                    }
                    while (!this.ParseResponse(Encoding.ASCII.GetString(buffer2, 0, buffer2.Length)));
                    client.Close();
                    List<Thread> list3 = this.mThreads;
                    lock (list3)
                    {
                        this.mGatewayAddress = remoteEP.Address;
                        this.mStatus = Status.Success;
                        this.mThreads.Remove(item);
                    }
                    this.mDiscover = null;
                }
                catch (Exception)
                {
                }
                return;
                if (client != null)
                {
                    client.Close();
                }
                List<Thread> mThreads = this.mThreads;
                lock (mThreads)
                {
                    this.mStatus = Status.Failure;
                    this.mThreads.Remove(item);
                }
                this.mDiscover = null;
                if (this.mStatus == Status.Success)
                {
                    break;
                }
            }
            if (this.mStatus != Status.Success)
            {
                Console.WriteLine("UPnP discovery failed. TNet won't be able to open ports automatically.");
            }
        }

        public void WaitForThreads()
        {
            for (int i = 0; (this.mThreads.size > 0) && (i < 0x7d0); i++)
            {
                Thread.Sleep(1);
            }
        }

        public IPAddress gatewayAddress
        {
            get
            {
                return this.mGatewayAddress;
            }
        }

        public bool hasThreadsActive
        {
            get
            {
                return (this.mThreads.size > 0);
            }
        }

        public Status status
        {
            get
            {
                return this.mStatus;
            }
        }

        private class ExtraParams
        {
            public string action;
            public UPnP.OnPortRequest callback;
            public int port;
            public ProtocolType protocol;
            public string request;
            public Thread th;
        }

        public delegate void OnPortRequest(UPnP up, int port, ProtocolType protocol, bool success);

        public enum Status
        {
            Inactive,
            Searching,
            Success,
            Failure
        }
    }
}

